﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GenericStateMachine.Test
{
    public class SampleService
    {
        #region state machine

        #region interfaces

        internal enum ServiceState
        {
            [State(typeof(ServiceStateCreated), StateGroup.Initial)]
            Created,

            [State(typeof(ServiceStateStarting))]
            Starting,

            [State(typeof(ServiceStateRequestWaiting))]
            RequestWaiting,

            [State(typeof(ServiceStateRequestHandling))]
            RequestHandling,

            [State(typeof(ServiceStateStopped))]
            Stopped,

            [State(typeof(ServiceStateAborted))]
            Aborted,
        }

        internal interface IServiceStateContext : IStateContext<IServiceStateContext, IServiceState, ServiceState>
        {
            // Context --> State
            void Start();
            void HandleRequest(string param);
            void HandleCallback(string param);
            void Stop();
            void Abort();
            bool IsBusy();

            // State --> Context
            void StartCore();
            void HandleRequestCore(string param);
            void HandleCallbackCore(string param);
            void StopCore();
            void AbortCore();
        }

        internal interface IServiceState : IState<IServiceStateContext, IServiceState, ServiceState>
        {
            ServiceState Start();
            ServiceState HandleRequest(string param);
            ServiceState HandleCallback(string param);
            ServiceState Stop();
            ServiceState Abort();

            Tuple<ServiceState, bool> IsBusy();
        }

        #endregion

        #region context class

        internal class ServiceStateContext : StateContextBase<IServiceStateContext, IServiceState, ServiceState>,
            IServiceStateContext
        {
            private readonly SampleService parent;

            public ServiceStateContext(SampleService parent)
            {
                this.parent = parent;
            }

            public void Start()
            {
                var action = GetHandlerAction(s => s.Start());
                action();
            }

            public void HandleRequest(string param)
            {
                var action = GetHandlerAction<string>((s, p) => s.HandleRequest(p));
                action(param);
            }

            public void HandleCallback(string param)
            {
                var action = GetHandlerAction<string>((s, p) => s.HandleCallback(p));
                action(param);
            }

            public void Stop()
            {
                var action = GetHandlerAction(s => s.Stop());
                action();
            }

            public void Abort()
            {
                var action = GetHandlerAction(s => s.Abort());
                action();
            }

            public bool IsBusy()
            {
                var func = GetHandlerFunc(s => s.IsBusy());
                var ret = func();

                return ret;
            }

            public void StartCore()
            {
                Debug.WriteLine("ServiceStateContext.StartCore");
                parent.StartCore();
            }

            public void HandleRequestCore(string param)
            {
                Debug.WriteLine("ServiceStateContext.HandleRequestCore({0})", new[] { param });
                parent.HandleRequestCore(param);
            }

            public void HandleCallbackCore(string param)
            {
                Debug.WriteLine("ServiceStateContext.HandleCallbackCore({0})", new[] { param });
                parent.HandleCallbackCore(param);
            }

            public void StopCore()
            {
                Debug.WriteLine("ServiceStateContext.StopCore");
                parent.StopCore();
            }

            public void AbortCore()
            {
                Debug.WriteLine("ServiceStateContext.AbortCore");
                parent.AbortCore();
            }

            protected override void OnStateChanging(IServiceState oldState, IServiceState newState, string eventName)
            {
                base.OnStateChanging(oldState, newState, eventName);

                Debug.WriteLine("ServiceStateContext.OnStateChanging {0} -> {1} ({2})", oldState.StateType, newState.StateType, eventName);
            }

            protected override void OnStateChanged(IServiceState oldState, IServiceState newState, string eventName)
            {
                base.OnStateChanged(oldState, newState, eventName);

                Debug.WriteLine("ServiceStateContext.OnStateChanged {0} -> {1} ({2})", oldState.StateType, newState.StateType, eventName);
            }
        }

        #endregion

        #region state classes

        internal abstract class ServiceStateBase : StateBase<IServiceStateContext, IServiceState, ServiceState>,
            IServiceState
        {
            public override ServiceState OnEntry()
            {
                var ret = base.OnEntry();

                Debug.WriteLine("{0}.{1}", GetType().Name, "OnEntry");

                return ret;
            }

            public override void OnExit()
            {
                base.OnExit();

                Debug.WriteLine("{0}.{1}", GetType().Name, "OnExit");
            }

            public virtual ServiceState Start()
            {
                return ThrowInvalidStateException(() => Start());
            }

            public virtual ServiceState HandleRequest(string param)
            {
                return ThrowInvalidStateException(() => HandleRequest(param));
            }

            public virtual ServiceState HandleCallback(string param)
            {
                return ThrowInvalidStateException(() => HandleCallback(param));
            }

            public virtual ServiceState Stop()
            {
                return ThrowInvalidStateException(() => Stop());
            }

            public abstract ServiceState Abort();

            public abstract Tuple<ServiceState, bool> IsBusy();
        }

        [StateClassFor(ServiceState.Created)]
        internal class ServiceStateCreated : ServiceStateBase
        {
            public override ServiceState Start()
            {
                var ret = ServiceState.Starting;
                return ret;
            }

            public override ServiceState Abort()
            {
                return ServiceState.Aborted;
            }

            public override Tuple<ServiceState, bool> IsBusy()
            {
                var ret = Tuple.Create(StateType, false);
                return ret;
            }
        }

        [StateClassFor(ServiceState.Starting)]
        internal class ServiceStateStarting : ServiceStateBase
        {
            public override ServiceState OnEntry()
            {
                var ret = base.OnEntry();

                Context.StartCore();

                ret = ServiceState.RequestWaiting;
                return ret;
            }

            public override ServiceState Abort()
            {
                return ThrowInvalidStateException(() => Abort());
            }

            public override Tuple<ServiceState, bool> IsBusy()
            {
                var ret = Tuple.Create(StateType, true);
                return ret;
            }
        }

        [StateClassFor(ServiceState.RequestWaiting)]
        internal class ServiceStateRequestWaiting : ServiceStateBase
        {
            public override ServiceState HandleRequest(string param)
            {
                Context.HandleRequestCore(param);
                var ret = ServiceState.RequestHandling;
                return ret;
            }

            public override ServiceState Stop()
            {
                var ret = ServiceState.Stopped;
                return ret;
            }

            public override ServiceState Abort()
            {
                var ret = ServiceState.Aborted;
                return ret;
            }

            public override Tuple<ServiceState, bool> IsBusy()
            {
                var ret = Tuple.Create(StateType, false);
                return ret;
            }
        }


        [StateClassFor(ServiceState.RequestHandling)]
        internal class ServiceStateRequestHandling : ServiceStateBase
        {
            public override ServiceState HandleCallback(string param)
            {
                Context.HandleCallbackCore(param);
                var ret = ServiceState.RequestWaiting;
                return ret;
            }

            public override ServiceState Abort()
            {
                var ret = ServiceState.Aborted;
                return ret;
            }

            public override Tuple<ServiceState, bool> IsBusy()
            {
                var ret = Tuple.Create(StateType, true);
                return ret;
            }
        }

        [StateClassFor(ServiceState.Stopped)]
        internal class ServiceStateStopped : ServiceStateBase
        {
            public override ServiceState OnEntry()
            {
                var ret = base.OnEntry();

                Context.StopCore();

                return ret;
            }

            public override ServiceState Abort()
            {
                // NOP
                var ret = StateType;
                return ret;
            }

            public override Tuple<ServiceState, bool> IsBusy()
            {
                var ret = Tuple.Create(StateType, false);
                return ret;
            }
        }

        [StateClassFor(ServiceState.Aborted)]
        internal class ServiceStateAborted : ServiceStateBase
        {
            public override ServiceState OnEntry()
            {
                var ret = base.OnEntry();

                Context.AbortCore();

                return ret;
            }

            public override ServiceState Abort()
            {
                // NOP
                var ret = StateType;
                return ret;
            }

            public override Tuple<ServiceState, bool> IsBusy()
            {
                var ret = Tuple.Create(StateType, false);
                return ret;
            }
        }

        #endregion

        #endregion 


        #region private fields

        private readonly ServiceStateContext stateMachine;

        #endregion

        #region constructor

        public SampleService()
        {
            stateMachine = new ServiceStateContext(this);
        }

        #endregion

        #region public methods

        public void Start()
        {
            stateMachine.Start();
        }

        public void Stop()
        {
            stateMachine.Stop();
        }

        public void Abort()
        {
            stateMachine.Abort();
        }

        public void HandleRequest(string param)
        {
            stateMachine.HandleRequest(param);

            Thread.Sleep(15);

            stateMachine.HandleCallback(string.Format("callback({0})", param));
        }

        #endregion

        #region private methods

        private void StartCore()
        {
            Debug.WriteLine("SampleService.StartCore");
        }

        private void HandleRequestCore(string param)
        {
            Debug.WriteLine("SampleService.HandleRequestCore({0})", new[] { param });
        }

        private void HandleCallbackCore(string param)
        {
            Debug.WriteLine("SampleService.HandleCallbackCore({0})", new[] { param });
        }

        private void StopCore()
        {
            Debug.WriteLine("SampleService.StopCore");
        }

        private void AbortCore()
        {
            Debug.WriteLine("SampleService.AbortCore");
        }

        #endregion
    }
}
